﻿using Microsoft.AspNetCore.Mvc;
using Shared;
using System.Buffers;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Threading;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Nito.AsyncEx;
using Microsoft.AspNetCore.Hosting;

namespace Serivces.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class UploadController : ControllerBase
    {
        /// <summary>
        /// 支持的Hash算法，优选算法请靠前
        /// </summary>
        private static string[] supportedHashAlgorithm = new[] { "MD5", "SHA1", "SHA256" };

        /// <summary>
        /// 文件写入锁的线程安全字典，每个上传任务对应一把锁
        /// </summary>
        private static readonly ConcurrentDictionary<string, AsyncLock> fileWriteLockerDict = new();
 

        /// <summary>
        /// 分片上传动作
        /// </summary>
        /// <param name="input">上传表单</param>
        /// <param name="fileChunkData">文件片段数据</param>
        /// <param name="requestAborted">请求取消令牌</param>
        /// <returns>片段上传结果</returns>
        [HttpPost, RequestSizeLimit(1024 * 1024 * 11)]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesDefaultResponseType]
        public async Task<IActionResult> Upload(
            [FromForm] FileChunkUploadInput input,
            [FromForm] IFormFile? fileChunkData,
            CancellationToken requestAborted)
        {
            switch (input.UploadType)
            {
                // 请求开始一个新的上传任务，协商上传参数
                case "startUpload":
                    {
                        //var trustedFileNameForDisplay =
                        //    WebUtility.HtmlEncode(fileChunkData?.FileName ?? input.FileName);

                        // 选择双方都支持的优选Hash算法
                        var selectedHashAlgorithm = supportedHashAlgorithm
                            .Intersect(input.AllowedHashAlgorithm ?? Enumerable.Empty<string>())
                            .FirstOrDefault();

                        // 验证必要的表单数据
                        if (selectedHashAlgorithm is null or "")
                        {
                            ModelState.AddModelError<FileChunkUploadInput>(x => x.AllowedHashAlgorithm, "can not select hash algorithm");
                        }

                        if (input.FileSize is null)
                        {
                            ModelState.AddModelError<FileChunkUploadInput>(x => x.FileSize, "must have value for start、upload and complete");
                        }

                        if (ModelState.ErrorCount > 0)
                        {
                            return ValidationProblem(ModelState);
                        }

                        // 使用随机文件名提高安全性，并把文件名作为任务代码使用
                        var trustedFileNameForFileStorage = Path.GetRandomFileName();

                        var savePath = Path.Combine(
                            "ContentRootPath",
                            "EnvironmentName",
                            "unsafe_uploads",
                            trustedFileNameForFileStorage);

                        var savePathWithFile = Path.Combine(
                            savePath,
                            $"{input.FileName}.tmp");

                        if (!Directory.Exists(savePath))
                        {
                            Directory.CreateDirectory(savePath);
                        }

                        // 根据表单创建对应大小的文件
                        await using (var fs = new FileStream(savePathWithFile, FileMode.Create))
                        {
                            fs.SetLength(input.FileSize!.Value);
                            await fs.FlushAsync();
                        }

                        // 设置锁
                        fileWriteLockerDict.TryAdd(trustedFileNameForFileStorage, new());

                        // 返回协商结果
                        return Ok(new FileChunkUploadStartReault
                        {
                            UploadTaskCode = trustedFileNameForFileStorage,
                            SelectedHashAlgorithm = selectedHashAlgorithm!
                        });
                    }

                // 上传文件片段
                case "uploadChunk":
                    // 验证表单
                    if (!fileWriteLockerDict.TryGetValue(input.UploadTaskCode!, out var _))
                    {
                        ModelState.AddModelError<FileChunkUploadInput>(x => x.UploadTaskCode, $"file upload task with code {input.UploadTaskCode} is not exists");
                        return ValidationProblem(ModelState);
                    }

                    // 使用内存池缓冲数据，注意使用using释放内存
                    using (var pooledMemory = MemoryPool<byte>.Shared.Rent((int)fileChunkData!.Length))
                    {
                        // 使用切片语法获取精准大小的内存缓冲区装载上传的数据
                        var buffer = pooledMemory.Memory[..(int)fileChunkData!.Length];
                        var readBytes = await fileChunkData.OpenReadStream().ReadAsync(buffer, requestAborted);
                        var readBuffer = buffer[..readBytes];

                        Debug.Assert(readBytes == fileChunkData!.Length);

                        // 校验Hash
                        var hash = input.HashAlgorithm switch
                        {
                            "SHA1" => SHA1.HashData(readBuffer.Span),
                            "SHA256" => SHA256.HashData(readBuffer.Span),
                            "MD5" => MD5.HashData(readBuffer.Span),
                            _ => Array.Empty<byte>()
                        };

                        if (hash.ToHexString() != input.HashValue)
                        {
                            ModelState.AddModelError<FileChunkUploadInput>(x => x.HashValue, "hash does not match");
                            return ValidationProblem(ModelState);
                        }

                        var savePath = Path.Combine(
                            "ContentRootPath",
                            "EnvironmentName",
                            "unsafe_uploads",
                            input.UploadTaskCode!);

                        var savePathWithFile = Path.Combine(
                            savePath,
                            $"{input.FileName}.tmp");

                        // 使用锁写入数据，文件流不支持写共享，必须串行化
                        if (fileWriteLockerDict.TryGetValue(input.UploadTaskCode!, out var locker))
                        {
                            using (await locker.LockAsync())
                            {
                                await using (var fs = new FileStream(savePathWithFile, FileMode.Open, FileAccess.Write))
                                {
                                    // 定位文件流
                                    fs.Seek(input.FileChunkOffset!.Value, SeekOrigin.Begin);

                                    await fs.WriteAsync(readBuffer, requestAborted);
                                    await fs.FlushAsync();
                                }
                            }
                        }
                    }

                    return Ok();

                // 取消上传
                case "cancelUpload":
                    // 验证表单
                    if (!fileWriteLockerDict.TryGetValue(input.UploadTaskCode!, out var _))
                    {
                        ModelState.AddModelError<FileChunkUploadInput>(x => x.UploadTaskCode, $"file upload task with code {input.UploadTaskCode} is not exists");
                        return ValidationProblem(ModelState);
                    }

                    {
                        var deletePath = Path.Combine(
                            "ContentRootPath",
                            "EnvironmentName",
                            "unsafe_uploads",
                            input.UploadTaskCode!);

                        // 删除文件，清除锁
                        if (fileWriteLockerDict.TryGetValue(input.UploadTaskCode!, out var locker))
                        {
                            using (await locker.LockAsync())
                            {
                                if (Directory.Exists(deletePath))
                                {
                                    var dir = new DirectoryInfo(deletePath);
                                    dir.Delete(true);
                                }

                                fileWriteLockerDict.TryRemove(input.UploadTaskCode!, out _);
                            }
                        }
                    }

                    return Ok();

                // 完成上传
                case "completeUpload":
                    // 验证表单
                    if (!fileWriteLockerDict.TryGetValue(input.UploadTaskCode!, out var _))
                    {
                        ModelState.AddModelError<FileChunkUploadInput>(x => x.UploadTaskCode, $"file upload task with code {input.UploadTaskCode} is not exists");
                        return ValidationProblem(ModelState);
                    }

                    {
                        var savePath = Path.Combine(
                            "ContentRootPath",
                            "EnvironmentName",
                            "unsafe_uploads",
                            input.UploadTaskCode!);

                        // 去除文件的临时扩展名，清除锁
                        var savePathWithFile = Path.Combine(savePath, $"{input.FileName}.tmp");

                        var fi = new FileInfo(savePathWithFile);
                        fi.MoveTo(Path.Combine(savePath, input.FileName));

                        fileWriteLockerDict.TryRemove(input.UploadTaskCode!, out _);
                    }

                    return Ok();
                default:
                    return BadRequest();
            }
        }
    }
}
